package pCloud;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.sun.xacml.Obligation;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.ResponseCtx;
import com.sun.xacml.ctx.Result;

import pCloud.ResourceConnection;
import pCloud.ResourceController;
import pCloud.SQLResourceConnection;
import pCloud.SimplePDP;
import pCloud.SimplePEPResponse;


/**
 * Simple PEP, must be extended
 * @author anhdinh
 *
 */
public class PEPSkeleton {

	static final String DEFAULT_RESOURCE_ID = SQLResourceConnection.DEFAULT_DATABASE;
	SimplePDP pdp;
	ResourceController resourceController;
	protected HashMap<URI, Obligation> obligationMap = new HashMap<URI, Obligation>();
	
	protected Result result;
	protected RequestCtx requestCtx; 
	
	/**
	 * @return the SQL query 
	 */
	public SimplePEPResponse evaluateRequest(String requestFile) throws Exception{
		pdp.setRequestCtx(requestFile);
    	
		this.requestCtx = pdp.getRequestContext();
    	
    	ResponseCtx outputs = pdp.evaluate();
    	
    	Set results = outputs.getResults();
    	
    	java.util.Iterator it = results.iterator();
    	ResourceConnection rc = null;
    	
    	while (it.hasNext()){
    		result = (Result)it.next();
    		System.out.println("Permission = "+getDecision(result.getDecision()));		   		
    		if (result.getDecision()!=Result.DECISION_PERMIT)
    			return null;
    		else
    			processResult(result, requestCtx);
    	}
    	
    	//Go through the obligations
    	Iterator temp = result.getObligations().iterator();
    	Obligation obligation;
    	
    	while (temp.hasNext()){
    		obligation = (Obligation)temp.next();
    		if (obligationMap.containsKey(obligation.getId()))
    			throw new Exception("Obligation ID must be unique");
    		obligationMap.put(obligation.getId(), obligation);
    	}
    	
    	// if there's a approximation obligation, process the result in PEPApproximation
    	return null;
	}
	
	protected String constructSelectClause(Result result, RequestCtx requestCtx) throws Exception{
		ArrayList<AttributeValue> requestedCols = this.getAttributeValue(
				PCloudAttributeAndType.RESOURCE_COLUMN_ID,
				requestCtx.getResource()); 
		if (requestedCols.size()==0) 
			return "select * from ";
		else
			return "select "+columnQuery(result, requestedCols)+" from ";
	}
	
	/**
	 * Check if there's obligation applied to the column. If there is, construct the part of the query with the constrain
	 * 
	 * Assume that there'll be at most one aggregation function, applied to all columns
	 * @throws Exception 
	 */
	private String columnQuery(Result result, ArrayList<AttributeValue> columns) throws Exception{
					
		//First, check the returned obligation for the aggregation function
		
		Obligation obligation = obligationMap.get(new URI(PCloudAttributeAndType.OBLIGATION_COLUMN_AGG_ID));
		
		String aggregationFunc = this.getAggregationFunction(obligation);		
		String query = "";
		if (aggregationFunc == null){
			for (int i=0; i<columns.size()-1; i++)
				query = query + ((StringAttribute)columns.get(i)).getValue()+", ";
			query = query + ((StringAttribute)columns.get(columns.size()-1)).getValue()+" ";
		}
		else{
			for (int i=0; i<columns.size()-1; i++)
				query = query + aggregationFunc+"("+((StringAttribute)columns.get(i)).getValue()+"), ";
			query = query + aggregationFunc+"("+((StringAttribute)columns.get(columns.size()-1)).getValue()+") ";
		}
		return query; 
		
	}
		
	
	/**
	 * @param obligations
	 * @return the aggregation function, null if doesn't exist. We assume there's only 1 aggregation function
	 * in the obligations
	 * @throws Exception 
	 */
	protected String getAggregationFunction(Obligation obligation) throws Exception{
		if (obligation==null)
			return null;
		
		if (!obligation.getId().equals(new URI(PCloudAttributeAndType.OBLIGATION_COLUMN_AGG_ID)))
			return null;
		
		List assignments;
		Attribute att;
		assignments = obligation.getAssignments();			
		if (assignments.size()==0)
			return null;
		else
		{
			//go through all the assignment attribute and apply ones with the ID ...-aggregation-id
			for (int i=0; i<assignments.size(); i++){
				att = (Attribute)assignments.get(i);
				if (att.getId().equals(new URI(PCloudAttributeAndType.OBLIGATION_ATT_AGG))){
					return ((StringAttribute)att.getValue()).getValue();						
				}					
			}
		}
				
		return null;
	}
	
	/**
	 * @param attId
	 * @param atts
	 * @return the value of the specified attribute, from the given set of attributes
	 * @throws URISyntaxException 
	 */
	protected ArrayList<AttributeValue> getAttributeValue(String attId, Set atts) throws URISyntaxException{
		Iterator it = atts.iterator();
		Attribute att;
		ArrayList<AttributeValue> values = new ArrayList<AttributeValue>(); 
		
		while (it.hasNext()){
			att = (Attribute)it.next();
			if (att.getId().equals(new URI(attId))){
				values.add(att.getValue());
			}
		}
		return values;
	}
	
	protected String getDatabaseId(List<AttributeValue> values) throws Exception{
		if (values.size()==0) 
			throw new Exception("no datbase value");
		Iterator it = values.iterator();
		return ((StringAttribute)it.next()).getValue();
	}
	
	protected String getTableId(List<AttributeValue> values) throws Exception{
		if (values.size()==0) 
			throw new Exception("no table value");
		Iterator it = values.iterator();
		return ((StringAttribute)it.next()).getValue();
	}
	
	protected void printResultSet(OutputStream out, ResultSet results) throws SQLException{
		int c = results.getMetaData().getColumnCount();
		PrintStream ps = new PrintStream(out);
		for (int i=0; i<c; i++)
			ps.print(results.getMetaData().getColumnName(i+1)+"\t");
		
		ps.println();
		while (results.next())
		{
			for (int i=1; i<=c; i++)
				ps.print(results.getObject(i)+"\t");
			ps.println();
		}
	}
	
	protected String getDecision(int dec){
		String res;
		if (dec == Result.DECISION_DENY)
			res = "Deny";
		else
			if (dec == Result.DECISION_INDETERMINATE)
				res = "Intermediate";
			else
				if (dec == Result.DECISION_NOT_APPLICABLE)
					res = "Not Applicable";
				else
					res = "Permit";
		return res;
	}
	
	
	protected void processResult(Result result, RequestCtx requestCtx) throws Exception{
		
	}
}
